﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO;
using System.Windows.Forms;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using ModelViewerAux;

namespace ModelViewer
{
    /// <summary>
    /// This class exports the animation for a model in .xanal format. That's
    /// an XML format that goes with the .xmodl format and is convenient for 
    /// use with OpenGL.
    /// </summary>
    public class AnimationExporter
    {
        private ClipPlayer currentPlayer = null;
        private string fileName = "";
        private XmodlOptions options;

        /// <summary>
        /// Constructor
        /// </summary>
        public AnimationExporter()
        {
        }

        /// <summary>
        /// Export the model to a given filename
        /// </summary>
        /// <param name="fileName"></param>
        public bool Export(TreeViewer treeViewer, string fileName, XmodlOptions options)
        {
            this.fileName = fileName;
            this.options = options;

            // Is there a current clip player?
            currentPlayer = null;
            if (treeViewer.SelectedClipPlayer != null)
            {
                currentPlayer = treeViewer.SelectedClipPlayer;
            }
            else if (treeViewer.SelectedModel != null)
            {
                // If no selected clip, we use the clip from the model if it 
                // only has one clip.
                ViewerModel model = treeViewer.SelectedModel;
                if (model.ClipPlayers.Count > 0)
                {
                    currentPlayer = model.ClipPlayers[0];
                }
            }

            // Is there an active clip?
            if (currentPlayer == null)
            {
                MessageBox.Show("No currently selected animation clip.", "Animation Export",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }

            // What is the assigned model?
            ViewerModel assignedModel = currentPlayer.Model;
            if (assignedModel == treeViewer.SelectedModel)
            {
                if (MessageBox.Show("This clip is assigned to the model it was loaded with, not a " +
                    "base pose model. Are you sure you want to export?", "Animation Export",
                    MessageBoxButtons.OKCancel, MessageBoxIcon.Hand) != DialogResult.OK)
                {
                    return false;
                }
            }

            // Determine a name. Take 001 is the 3D Studio Max name,
            // so I use the model name, instead if I see that.
            string name = currentPlayer.Clip.Name;
            if (name == "Take 001")
            {
                name = treeViewer.SelectedModel.Name;
                if (name.ToLower().EndsWith(".fbx"))
                {
                    name = name.Substring(0, name.Length - 4);
                }
            }

            
            try
            {
                FileStream stream = new FileStream(fileName, FileMode.Create);
                XmlTextWriter xml = new XmlTextWriter(stream, System.Text.Encoding.UTF8);

                xml.Formatting = Formatting.Indented;
                xml.WriteProcessingInstruction("xml", "version='1.0' encoding='UTF-8'");
                xml.WriteStartElement("xanal");

                // xmodl format version
                xml.WriteAttributeString("version", ModelExporter.Version);
                if (options.CompactFormat)
                {
                    xml.WriteAttributeString("compact", "true");
                    xml.WriteAttributeString("endian", BitConverter.IsLittleEndian ? "little" : "big");
                }

                if (options.CompactFormat)
                {
                    WriteClipCompact(xml, currentPlayer, name);
                }
                else
                {
                    WriteClip(xml, currentPlayer, name);
                }

                

                xml.WriteEndElement();          // </xanal>
                xml.Close();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString(), "Error writing exported animation file.",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);

                return false;
            }


            return true;
        }

        /// <summary>
        ///  Write a clip to XML. 
        /// </summary>
        /// <param name="xml">The writer we are writing to</param>
        /// <param name="player">The clip player containing the clip</param>
        /// <param name="name">The name of the clip to save</param>
        private void WriteClip(XmlWriter xml, ClipPlayer player, string name)
        {
            AnimationClip clip = player.Clip;

            xml.WriteStartElement("clip");
            xml.WriteAttributeString("name", name);
            xml.WriteAttributeString("dur", clip.Duration.ToString());

            foreach (AnimationClip.Bone bone in clip.Bones)
            {
                // Find the bone in the assigned model
                Bone assignedBone = player.Model.FindBone(bone.Name);
                if (assignedBone != null)
                {
                    xml.WriteStartElement("bone");
                    xml.WriteAttributeString("name", bone.Name);
                    xml.WriteAttributeString("cnt", bone.Keyframes.Count.ToString());

                    foreach (AnimationClip.Keyframe keyframe in bone.Keyframes)
                    {
                        // Get the transform for the keyframe
                        Matrix tran = keyframe.Transform;

                        // Compute values relative to the bind pose
                        Vector3 scale;
                        Quaternion rotation;
                        Vector3 translation;

                        assignedBone.GetCompleteTransform(tran, out scale, out rotation, out translation);

                        xml.WriteStartElement("k");
                        xml.WriteAttributeString("tm", keyframe.Time.ToString());
                        if (translation.Length() > 0.01)
                        {
                            xml.WriteAttributeString("t", translation.X +
                                " " + translation.Y + " " + translation.Z);
                        }

                        xml.WriteAttributeString("r", rotation.W + " " +
                            rotation.X + " " + rotation.Y + " " +
                            rotation.Z);

                        Vector3 s1 = scale - Vector3.One;
                        if (s1.Length() > 0.01)
                        {
                            xml.WriteAttributeString("s", scale.X + " " +
                                scale.Y + " " +
                                scale.Z);
                        }

                        xml.WriteEndElement();
                    }

                    xml.WriteEndElement();
                }
            }


            xml.WriteEndElement();          // </clip>
        }



        /// <summary>
        ///  Write a clip to XML in Compact format.
        /// </summary>
        /// <param name="xml">The writer we are writing to</param>
        /// <param name="player">The clip player containing the clip</param>
        /// <param name="name">The name of the clip to save</param>
        private void WriteClipCompact(XmlWriter xml, ClipPlayer player, string name)
        {
            AnimationClip clip = player.Clip;

            xml.WriteStartElement("clip");
            xml.WriteAttributeString("name", name);
            xml.WriteAttributeString("dur", clip.Duration.ToString());

            foreach (AnimationClip.Bone bone in clip.Bones)
            {
                // Find the bone in the assigned model
                Bone assignedBone = player.Model.FindBone(bone.Name);
                if (assignedBone != null)
                {
                    xml.WriteStartElement("bone-compact");
                    xml.WriteAttributeString("name", bone.Name);
                    xml.WriteAttributeString("cnt", bone.Keyframes.Count.ToString());

                    // Each keyframe consists of:
                    // time - 1 float
                    // translation - 3 floats
                    // rotation - 4 floats
                    // This is a total of 8 floats or 32 bytes per keyframe
                    byte[] buffer = new byte[bone.Keyframes.Count * 8 * 4];
                    int bndx = 0;

                    foreach (AnimationClip.Keyframe keyframe in bone.Keyframes)
                    {
                        // Get the transform for the keyframe
                        Matrix tran = keyframe.Transform;

                        // Compute values relative to the bind pose
                        Vector3 scale;
                        Quaternion rotation;
                        Vector3 translation;

                        assignedBone.GetCompleteTransform(tran, out scale, out rotation, out translation);

                        bndx = WriteCompactFloat(buffer, bndx, (float)keyframe.Time);

                        bndx = WriteCompactFloat(buffer, bndx,  translation.X);
                        bndx = WriteCompactFloat(buffer, bndx,  translation.Y);
                        bndx = WriteCompactFloat(buffer, bndx,  translation.Z);

                        bndx = WriteCompactFloat(buffer, bndx, rotation.W);
                        bndx = WriteCompactFloat(buffer, bndx, rotation.X);
                        bndx = WriteCompactFloat(buffer, bndx, rotation.Y);
                        bndx = WriteCompactFloat(buffer, bndx, rotation.Z);

                        // Not supporting scale at this time
                        // xml.WriteAttributeString("s", scale.X + " " +
                        //    scale.Y + " " +
                        //    scale.Z);
                    }

                    xml.WriteBase64(buffer, 0, buffer.Length);
                    xml.WriteEndElement();
                }
            }


            xml.WriteEndElement();          // </clip>
        }

        private int WriteCompactFloat(byte[] buffer, int bndx, float val)
        {
            byte[] f = BitConverter.GetBytes(val);
            for (int j = 0; j < 4; j++)
            {
                buffer[bndx++] = f[j];
            }

            return bndx;
        }
    }
}
